using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace CGEx1
{
  public class Game1 : Microsoft.Xna.Framework.Game
  {
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;
    Camera mainCamera, smallCamera;
    VertexBuffer circleVertBuffer, frustumVertices;
    IndexBuffer frustumIndexBuffer;
    Effect effect;
    private Vector4 nearBL = new Vector4(-1.0f,-1.0f,0.0f,0.0f);
    private Vector4 nearUL = new Vector4(-1.0f,1.0f,0.0f,0.0f);
    private Vector4 nearBR = new Vector4(1.0f,-1.0f,0.0f,0.0f);
    private Vector4 nearUR = new Vector4(1.0f,1.0f,0.0f,0.0f);
    private Vector4 farBL = new Vector4(-1.0f, -1.0f, .75f, 0.0f);
    private Vector4 farUL = new Vector4(-1.0f, 1.0f, .75f, 0.0f);
    private Vector4 farBR = new Vector4(1.0f, -1.0f, .75f, 0.0f);
    private Vector4 farUR = new Vector4(1.0f, 1.0f, .75f, 0.0f);

    public Game1()
    {
      graphics = new GraphicsDeviceManager(this);
      Content.RootDirectory = "Content";
      graphics.PreferredBackBufferWidth = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Width;
      graphics.PreferredBackBufferHeight = GraphicsAdapter.DefaultAdapter.CurrentDisplayMode.Height;
      graphics.IsFullScreen = true;
    }

    protected override void Initialize()
    {
      base.Initialize();
    }

    protected override void LoadContent()
    {
      VertexPositionColor[] vertices  = new VertexPositionColor[8];
      short[] indices = new short[24];

      indices[0]  = 0;
      indices[1]  = 1;
      indices[2]  = 1;
      indices[3]  = 2;
      indices[4]  = 2;
      indices[5]  = 3;
      indices[6]  = 3;
      indices[7]  = 0;
      indices[8]  = 4;
      indices[9]  = 5;
      indices[10] = 5;
      indices[11] = 6;
      indices[12] = 6;
      indices[13] = 7;
      indices[14] = 7;
      indices[15] = 4;
      indices[16] = 0;
      indices[17] = 4;
      indices[18] = 1;
      indices[19] = 5;
      indices[20] = 2;
      indices[21] = 6;
      indices[22] = 3;
      indices[23] = 7;

      VertexPositionColor frustumNearBLVertex = new VertexPositionColor(
        new Vector3(nearBL.X, nearBL.Y, nearBL.Z), Color.White);
      VertexPositionColor frustumNearULVertex = new VertexPositionColor(
        new Vector3(nearUL.X, nearUL.Y, nearUL.Z), Color.White);
      VertexPositionColor frustumNearURVertex = new VertexPositionColor(
        new Vector3(nearUR.X, nearUR.Y, nearUR.Z), Color.White);
      VertexPositionColor frustumNearBRVertex = new VertexPositionColor(
        new Vector3(nearBR.X, nearBR.Y, nearBR.Z), Color.White);
      vertices[0] = frustumNearBLVertex;
      vertices[1] = frustumNearULVertex;
      vertices[2] = frustumNearURVertex;
      vertices[3] = frustumNearBRVertex;

      //create far plane vertices
      VertexPositionColor frustumFarBLVertex = new VertexPositionColor(
        new Vector3(farBL.X, farBL.Y, farBL.Z), Color.White);
      VertexPositionColor frustumFarULVertex = new VertexPositionColor(
        new Vector3(farUL.X, farUL.Y, farUL.Z), Color.White);
      VertexPositionColor frustumFarURVertex = new VertexPositionColor(
        new Vector3(farUR.X, farUR.Y, farUR.Z), Color.White);
      VertexPositionColor frustumFarBRVertex = new VertexPositionColor(
        new Vector3(farBR.X, farBR.Y, farBR.Z), Color.White);
      vertices[4] = frustumFarBLVertex;
      vertices[5] = frustumFarULVertex;
      vertices[6] = frustumFarURVertex;
      vertices[7] = frustumFarBRVertex;
      spriteBatch = new SpriteBatch(GraphicsDevice);
      circleVertBuffer = new VertexBuffer(GraphicsDevice, VertexPositionColor.VertexDeclaration, 100, BufferUsage.WriteOnly);
      frustumVertices = new VertexBuffer(GraphicsDevice, VertexPositionColor.VertexDeclaration, 8, BufferUsage.WriteOnly);
      frustumIndexBuffer = new IndexBuffer(GraphicsDevice, typeof(short), 24, BufferUsage.WriteOnly);

      frustumVertices.SetData(vertices);
      frustumIndexBuffer.SetData(indices);
      

      Func<float, Vector3> P = a => new Vector3((float)Math.Cos(a * 2 * 3.14f), 0.0f, (float)Math.Sin(a * 2 * 3.14f));
      Func<float, Color> C = a => new Color(P(a));

      VertexPositionColor[] circleVertices = 
               Enumerable.Range(0, 100)
              .Select(i => i / 99.0f)
              .Select(a => new VertexPositionColor(P(a), C(a)))
              .ToArray();

      for (int i = 0; i < circleVertices.Length; i++)
      {
        Matrix xrot = Matrix.CreateRotationX(MathHelper.PiOver2);
        circleVertices[i].Position = Vector3.Transform(circleVertices[i].Position, xrot);
      }

      circleVertBuffer.SetData(circleVertices);

      effect = Content.Load<Effect>("CustomEffect");
      var smallViewport = GraphicsDevice.Viewport;
      smallViewport.X = 0;
      smallViewport.Y = 0;
      smallViewport.Width = 300;
      smallViewport.Height = 300;
      mainCamera = new Camera(true, new Vector3(0.0f, 0.0f, 20.0f), GraphicsDevice.Viewport);
      smallCamera = new Camera(false, new Vector3(0.0f, 0.0f, 10.0f), smallViewport);
    }

    private void InputDetection()
    {
      KeyboardState keyboardState = Keyboard.GetState();
      if (keyboardState.IsKeyDown(Keys.Escape))
        this.Exit();
    }

    protected override void Update(GameTime gameTime)
    {
      InputDetection();
      mainCamera.Update();
      smallCamera.Update();
      base.Update(gameTime);
    }


    protected override void Draw(GameTime gameTime)
    {
      GraphicsDevice.Viewport = mainCamera.viewport;
      GraphicsDevice.Clear(Color.CornflowerBlue);
      drawAll(mainCamera);
      drawFrustum(mainCamera, smallCamera);

      GraphicsDevice.Viewport = smallCamera.viewport;
      drawAll(smallCamera);
      drawFrustum(smallCamera, mainCamera);

      base.Draw(gameTime);
    }

    private void drawFrustum(Camera mainCamera, Camera relativeCamera)
    {
      Matrix invVP = Matrix.Invert(relativeCamera.viewMatrix * relativeCamera.projectionMatrix);

      effect.Parameters["World"].SetValue(invVP);
      effect.Parameters["View"].SetValue(mainCamera.viewMatrix);
      effect.Parameters["Projection"].SetValue(mainCamera.projectionMatrix);
      effect.CurrentTechnique.Passes[0].Apply();


      GraphicsDevice.SetVertexBuffer(frustumVertices);
      GraphicsDevice.Indices = frustumIndexBuffer;
      GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.LineList, 0, 0, 8, 0, 12);
    }

    private void drawAll(Camera camera)
    {
      effect.Parameters["World"].SetValue(Matrix.Identity);
      effect.Parameters["View"].SetValue(camera.viewMatrix);
      effect.Parameters["Projection"].SetValue(camera.projectionMatrix);
      effect.CurrentTechnique.Passes[0].Apply();

      GraphicsDevice.SetVertexBuffer(circleVertBuffer);

      GraphicsDevice.DrawPrimitives(PrimitiveType.LineStrip, 0, circleVertBuffer.VertexCount - 1);
    }
  }
}